//===--- DataflowDiagnostics.cpp - Emits diagnostics based on SIL analysis ===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "swift/AST/ASTContext.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Stmt.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/SILConstants.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILLocation.h"
#include "swift/SIL/SILModule.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/ConstExpr.h"

using namespace swift;

template<typename...T, typename...U>
static void diagnose(ASTContext &Context, SourceLoc loc, Diag<T...> diag,
              U &&...args) {
  Context.Diags.diagnose(loc,
                         diag, std::forward<U>(args)...);
}

static void diagnoseMissingReturn(const UnreachableInst *UI,
                                  ASTContext &Context) {
  const SILBasicBlock *BB = UI->getParent();
  const SILFunction *F = BB->getParent();
  SILLocation FLoc = F->getLocation();

  Type ResTy;
  BraceStmt *BS;

  if (auto *FD = FLoc.getAsASTNode<FuncDecl>()) {
    ResTy = FD->getResultInterfaceType();
    BS = FD->getBody(/*canSynthesize=*/false);
  } else if (auto *CD = FLoc.getAsASTNode<ConstructorDecl>()) {
    ResTy = CD->getResultInterfaceType();
    BS = FD->getBody();
  } else if (auto *CE = FLoc.getAsASTNode<ClosureExpr>()) {
    ResTy = CE->getResultType();
    BS = CE->getBody();
  } else {
    llvm_unreachable("unhandled case in MissingReturn");
  }

  SILLocation L = UI->getLoc();
  assert(L && ResTy);
  if (!BS->empty()) {
    auto element = BS->getLastElement();
    if (auto expr = element.dyn_cast<Expr *>()) {
      if (expr->getType()->getRValueType()->isEqual(ResTy)) {
        Context.Diags.diagnose(
          expr->getStartLoc(),
          diag::missing_return_last_expr, ResTy,
          FLoc.isASTNode<ClosureExpr>() ? 1 : 0)
        .fixItInsert(expr->getStartLoc(), "return ");
        return;
      }
    }
  }
  auto diagID = F->isNoReturnFunction(F->getTypeExpansionContext())
                    ? diag::missing_never_call
                    : diag::missing_return;
  diagnose(Context,
           L.getEndSourceLoc(),
           diagID, ResTy,
           FLoc.isASTNode<ClosureExpr>() ? 1 : 0);
}

static void diagnoseUnreachable(const SILInstruction *I,
                                ASTContext &Context) {
  if (auto *UI = dyn_cast<UnreachableInst>(I)) {
    SILLocation L = UI->getLoc();

    // Invalid location means that the instruction has been generated by SIL
    // passes, such as DCE. FIXME: we might want to just introduce a separate
    // instruction kind, instead of keeping this invariant.
    //
    // We also do not want to emit diagnostics for code that was
    // transparently inlined. We should have already emitted these
    // diagnostics when we process the callee function prior to
    // inlining it.
    if (!L || L.is<MandatoryInlinedLocation>())
      return;

    // The most common case of getting an unreachable instruction is a
    // missing return statement. In this case, we know that the instruction
    // location will be the enclosing function.
    if (L.isASTNode<AbstractFunctionDecl>() || L.isASTNode<ClosureExpr>()) {
      diagnoseMissingReturn(UI, Context);
      return;
    }

    if (auto *Guard = L.getAsASTNode<GuardStmt>()) {
      diagnose(Context, Guard->getBody()->getEndLoc(),
               diag::guard_body_must_not_fallthrough);
      return;
    }
  }
}

/// Issue diagnostics whenever we see Builtin.static_report(1, ...).
static void diagnoseStaticReports(const SILInstruction *I,
                                  SILModule &M) {

  // Find out if we are dealing with Builtin.staticReport().
  if (auto *BI = dyn_cast<BuiltinInst>(I)) {
    const BuiltinInfo &B = BI->getBuiltinInfo();
    if (B.ID == BuiltinValueKind::StaticReport) {

      // Report diagnostic if the first argument has been folded to '1'.
      OperandValueArrayRef Args = BI->getArguments();
      auto *V = dyn_cast<IntegerLiteralInst>(Args[0]);
      if (!V || V->getValue() != 1)
        return;

      diagnose(M.getASTContext(), I->getLoc().getSourceLoc(),
               diag::static_report_error);
    }
  }
}

/// Emit a diagnostic for `poundAssert` builtins whose condition is
/// false or whose condition cannot be evaluated.
static void diagnosePoundAssert(const SILInstruction *I,
                                SILModule &M,
                                ConstExprEvaluator &constantEvaluator) {
  auto *builtinInst = dyn_cast<BuiltinInst>(I);
  if (!builtinInst ||
      builtinInst->getBuiltinKind() != BuiltinValueKind::PoundAssert)
    return;

  SmallVector<SymbolicValue, 1> values;
  constantEvaluator.computeConstantValues({builtinInst->getArguments()[0]},
                                          values);
  SymbolicValue value = values[0];
  if (!value.isConstant()) {
    diagnose(M.getASTContext(), I->getLoc().getSourceLoc(),
             diag::pound_assert_condition_not_constant);

    // If we have more specific information about what went wrong, emit
    // notes.
    if (value.getKind() == SymbolicValue::Unknown)
      value.emitUnknownDiagnosticNotes(builtinInst->getLoc());
    return;
  }
  assert(value.getKind() == SymbolicValue::Integer &&
         "sema prevents non-integer #assert condition");

  APInt intValue = value.getIntegerValue();
  assert(intValue.getBitWidth() == 1 &&
         "sema prevents non-int1 #assert condition");
  if (intValue.isNullValue()) {
    auto *message = cast<StringLiteralInst>(builtinInst->getArguments()[1]);
    StringRef messageValue = message->getValue();
    if (messageValue.empty())
      messageValue = "assertion failed";
    diagnose(M.getASTContext(), I->getLoc().getSourceLoc(),
             diag::pound_assert_failure, messageValue);
    return;
  }
}

static void diagnoseUnspecializedPolymorphicBuiltins(SILInstruction *inst) {
  // We only validate if we are in a non-transparent function.
  if (inst->getFunction()->isTransparent())
    return;

  auto *bi = dyn_cast<BuiltinInst>(inst);
  if (!bi)
    return;

  auto kind = bi->getBuiltinKind();
  if (!kind)
    return;

  if (!isPolymorphicBuiltin(*kind))
    return;

  const auto &builtinInfo = bi->getBuiltinInfo();

  // First that the parameters were acceptable so we can emit a nice error to
  // guide the user.
  for (SILValue value : bi->getOperandValues()) {
    SILType type = value->getType();
    SourceLoc loc;
    if (auto *inst = value->getDefiningInstruction()) {
      loc = inst->getLoc().getSourceLoc();
    } else {
      loc = bi->getLoc().getSourceLoc();
    }

    if (!type.is<BuiltinType>() || !type.isTrivial(*bi->getFunction())) {
      diagnose(bi->getModule().getASTContext(), loc,
               diag::polymorphic_builtin_passed_non_trivial_non_builtin_type,
               type.getASTType());
      return;
    }
  }

  // Ok, we have a valid type for a polymorphic builtin. Make sure we actually
  // have a static overload for this type.
  PolymorphicBuiltinSpecializedOverloadInfo overloadInfo;
  bool ableToMapToStaticOverload = overloadInfo.init(bi);
  (void)ableToMapToStaticOverload;
  assert(ableToMapToStaticOverload);
  if (!overloadInfo.doesOverloadExist()) {
    diagnose(bi->getModule().getASTContext(), bi->getLoc().getSourceLoc(),
             diag::polymorphic_builtin_passed_type_without_static_overload,
             overloadInfo.staticOverloadIdentifier,
             getBuiltinName(builtinInfo.ID),
             overloadInfo.argTypes.front().getASTType());
    return;
  }

  // Otherwise, something happen that we did not understand. This can only
  // happen if we specialize the generic type in the builtin /after/ constant
  // propagation runs at -Onone but before dataflow diagnostics. This is an
  // error in implementation, so we assert.
  llvm_unreachable("Found generic builtin with known static overload that it "
                   "could be transformed to. Did this builtin get its generic "
                   "type specialized /after/ constant propagation?");
}

namespace {
class EmitDFDiagnostics : public SILFunctionTransform {
  ~EmitDFDiagnostics() override {}

  /// The entry point to the transformation.
  void run() override {
    // Don't rerun diagnostics on deserialized functions.
    if (getFunction()->wasDeserializedCanonical())
      return;

    SILModule &M = getFunction()->getModule();
    for (auto &BB : *getFunction()) {
      for (auto &I : BB) {
        diagnoseUnreachable(&I, M.getASTContext());
        diagnoseStaticReports(&I, M);
        diagnoseUnspecializedPolymorphicBuiltins(&I);
      }
    }

    if (M.getASTContext().LangOpts.EnableExperimentalStaticAssert) {
      SymbolicValueBumpAllocator allocator;
      ConstExprEvaluator constantEvaluator(allocator,
                                           getOptions().AssertConfig);
      for (auto &BB : *getFunction())
        for (auto &I : BB)
          diagnosePoundAssert(&I, M, constantEvaluator);
    }
  }
};

} // end anonymous namespace


SILTransform *swift::createEmitDFDiagnostics() {
  return new EmitDFDiagnostics();
}
